(MOT-4106) feat(github): GitHub CLI (gh) as an iii worker#529
Conversation
30 typed github::pr/issue/repo/run/workflow/release/search functions plus github::exec (argv passthrough, outcome as data) and github::api (any REST endpoint). Bounded-capture exec core with process-group timeout kill (shell's run_to_completion mechanics), GH_TOKEN via the configuration worker with ambient gh-auth fallback, golden-tested wire schemas, and read-only functions allowed in iii-permissions (mutations and both escape hatches stay approval-gated).
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
skill-check — worker0 verified, 47 skipped (no docs/).
Four for four. Nicely done. |
|
Warning Review limit reached
Next review available in: 42 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds a Rust-based GitHub worker backed by the ChangesGitHub worker
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@github/src/functions/pr.rs`:
- Around line 150-163: Update CreateRequest so head is a required String rather
than an Option, then adjust the PR creation flow to always pass the request’s
head value to gh pr create via --head. Ensure all construction and handling of
CreateRequest provide and propagate head explicitly.
In `@github/src/functions/search.rs`:
- Around line 45-48: Update search_args to insert the argument separator "--"
before query, ensuring leading-negative queries are treated as search terms
rather than flags while preserving the existing kind, JSON, and limit arguments.
In `@github/src/gh.rs`:
- Around line 103-125: Update the timeout branch around kill_process_group and
child.start_kill to terminate the entire gh process tree on Windows, using a
Windows job object or equivalent descendant cleanup before awaiting stdout_task
and stderr_task. Preserve existing behavior on Unix and non-Windows platforms,
and add a test covering a spawned descendant that keeps stdout or stderr open
past the timeout.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: d42fefe2-d135-4f07-9317-9c9803adb7da
⛔ Files ignored due to path filters (1)
github/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (58)
.github/workflows/create-tag.yml.github/workflows/release.ymlREADME.mdgithub/Cargo.tomlgithub/README.mdgithub/config.yamlgithub/iii-permissions.yamlgithub/iii.worker.yamlgithub/skills/SKILL.mdgithub/src/config.rsgithub/src/configuration.rsgithub/src/functions/actions.rsgithub/src/functions/issue.rsgithub/src/functions/mod.rsgithub/src/functions/passthrough.rsgithub/src/functions/pr.rsgithub/src/functions/release.rsgithub/src/functions/repo.rsgithub/src/functions/search.rsgithub/src/gh.rsgithub/src/lib.rsgithub/src/main.rsgithub/tests/contract.rsgithub/tests/golden/schemas/github.api.jsongithub/tests/golden/schemas/github.exec.jsongithub/tests/golden/schemas/github.issue.close.jsongithub/tests/golden/schemas/github.issue.comment.jsongithub/tests/golden/schemas/github.issue.create.jsongithub/tests/golden/schemas/github.issue.edit.jsongithub/tests/golden/schemas/github.issue.list.jsongithub/tests/golden/schemas/github.issue.view.jsongithub/tests/golden/schemas/github.pr.checks.jsongithub/tests/golden/schemas/github.pr.comment.jsongithub/tests/golden/schemas/github.pr.create.jsongithub/tests/golden/schemas/github.pr.diff.jsongithub/tests/golden/schemas/github.pr.edit.jsongithub/tests/golden/schemas/github.pr.list.jsongithub/tests/golden/schemas/github.pr.merge.jsongithub/tests/golden/schemas/github.pr.review.jsongithub/tests/golden/schemas/github.pr.view.jsongithub/tests/golden/schemas/github.release.create.jsongithub/tests/golden/schemas/github.release.list.jsongithub/tests/golden/schemas/github.release.view.jsongithub/tests/golden/schemas/github.repo.list.jsongithub/tests/golden/schemas/github.repo.view.jsongithub/tests/golden/schemas/github.run.cancel.jsongithub/tests/golden/schemas/github.run.list.jsongithub/tests/golden/schemas/github.run.rerun.jsongithub/tests/golden/schemas/github.run.view.jsongithub/tests/golden/schemas/github.search.code.jsongithub/tests/golden/schemas/github.search.issues.jsongithub/tests/golden/schemas/github.search.prs.jsongithub/tests/golden/schemas/github.search.repos.jsongithub/tests/golden/schemas/github.workflow.list.jsongithub/tests/golden/schemas/github.workflow.run.jsongithub/tests/schemas.rsgithub/tests/support/mod.rsiii-permissions.yaml
…b page Five read-only panels over the github worker: pull requests (state filter + inline CI checks rollup via github::pr::checks), issues, Actions runs, releases, and org-wide search (repos/issues/prs/code). Repo input persists in localStorage; data loads on demand (no polling — gh rate limits). Nav entry gated on worker presence like worktrees/browser/memory; mutations deliberately stay in agent flows behind the approval gate. Console bumped to 1.6.11.
…ch queries
Review follow-ups: `github::pr::create` now requires `head` (the worker runs
outside any checkout, so gh cannot infer a branch — schema-required beats a
runtime gh error), and `gh search` argv puts flags before `--` with the query
positional after it, so leading negative qualifiers ("-label:bug") stop being
swallowed as flags (verified against gh 2.94).
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@console/web/src/pages/Github/index.tsx`:
- Around line 46-144: Replace the bump-based remount in the Github page with a
refreshToken prop threaded into PrsPanel, IssuesPanel, RunsPanel, ReleasesPanel,
and SearchPanel. Update each panel’s fetcher callback dependencies to include
refreshToken so useGithubQuery refetches while preserving panel-local filters
and expansion state; keep repository and panel changes functioning normally
without using refreshToken in the container key.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a535744e-c458-4704-b4a2-58693fbb7b4b
⛔ Files ignored due to path filters (1)
console/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (21)
console/Cargo.tomlconsole/web/src/App.tsxconsole/web/src/hooks/use-github-status.test.tsconsole/web/src/hooks/use-github-status.tsconsole/web/src/hooks/use-hash-route.tsconsole/web/src/lib/conversations-context.tsxconsole/web/src/lib/github.test.tsconsole/web/src/lib/github.tsconsole/web/src/lib/nav-options.test.tsconsole/web/src/lib/nav-options.tsconsole/web/src/pages/Github/components/IssuesPanel.tsxconsole/web/src/pages/Github/components/PanelShell.tsxconsole/web/src/pages/Github/components/PrsPanel.tsxconsole/web/src/pages/Github/components/ReleasesPanel.tsxconsole/web/src/pages/Github/components/RunsPanel.tsxconsole/web/src/pages/Github/components/SearchPanel.tsxconsole/web/src/pages/Github/hooks/useGithubQuery.tsconsole/web/src/pages/Github/index.tsxgithub/src/functions/pr.rsgithub/src/functions/search.rsgithub/tests/golden/schemas/github.pr.create.json
🚧 Files skipped from review as they are similar to previous changes (3)
- github/tests/golden/schemas/github.pr.create.json
- github/src/functions/search.rs
- github/src/functions/pr.rs
| const [bump, setBump] = useState(0) | ||
|
|
||
| const commitRepo = () => { | ||
| const next = repoInput.trim() | ||
| setRepo(next) | ||
| saveGithubRepo(next) | ||
| } | ||
|
|
||
| const needsRepo = panel !== 'search' | ||
|
|
||
| return ( | ||
| <main | ||
| className="flex-1 flex flex-col min-h-0 overflow-hidden" | ||
| aria-label="github" | ||
| > | ||
| <header className="shrink-0 px-4 sm:px-6 lg:px-8 py-4 border-b border-rule flex flex-wrap items-center justify-between gap-3"> | ||
| <div> | ||
| <h1 className="font-mono text-[16px] font-semibold tracking-[-0.01em] text-ink lowercase"> | ||
| github | ||
| </h1> | ||
| <p className="font-mono text-[12px] text-ink-faint mt-0.5 lowercase"> | ||
| {available | ||
| ? repo || 'no repository selected' | ||
| : 'worker not connected'} | ||
| </p> | ||
| </div> | ||
| {available ? ( | ||
| <Button | ||
| variant="ghost" | ||
| size="sm" | ||
| onClick={() => setBump((b) => b + 1)} | ||
| className="gap-1.5" | ||
| > | ||
| <RefreshCw className="w-3.5 h-3.5" aria-hidden /> | ||
| refresh | ||
| </Button> | ||
| ) : null} | ||
| </header> | ||
|
|
||
| {available ? ( | ||
| <div className="shrink-0 px-4 sm:px-6 lg:px-8 py-3 border-b border-rule flex flex-wrap items-center gap-3"> | ||
| {needsRepo ? ( | ||
| <Input | ||
| value={repoInput} | ||
| onChange={setRepoInput} | ||
| onBlur={commitRepo} | ||
| onKeyDown={(e) => { | ||
| if (e.key === 'Enter') commitRepo() | ||
| }} | ||
| placeholder="owner/name" | ||
| preserveCase | ||
| aria-label="repository" | ||
| className="w-64 max-w-full" | ||
| /> | ||
| ) : null} | ||
| <ModeToggle<Panel> | ||
| value={panel} | ||
| onChange={setPanel} | ||
| options={PANEL_OPTIONS} | ||
| /> | ||
| </div> | ||
| ) : null} | ||
|
|
||
| <div className="flex-1 overflow-auto px-4 sm:px-6 lg:px-8 py-4"> | ||
| {!available ? ( | ||
| status.loading ? ( | ||
| <p className="font-mono text-[12px] lowercase text-ink-ghost"> | ||
| checking for the github worker... | ||
| </p> | ||
| ) : ( | ||
| <StatusPanel | ||
| variant="info" | ||
| icon={<AlertCircle className="w-full h-full" />} | ||
| headline="github worker not installed" | ||
| detail="this page needs the optional github worker (and the gh CLI on its host). run: iii worker add github" | ||
| /> | ||
| ) | ||
| ) : needsRepo && repo === '' ? ( | ||
| <EmptyState | ||
| icon={FolderGit2} | ||
| title="no repository selected" | ||
| description="enter a repository above as owner/name — e.g. iii-hq/workers — and press enter. the search panel works without one." | ||
| /> | ||
| ) : ( | ||
| // Remount on repo/panel/refresh so every fetch hook restarts clean. | ||
| <div key={`${panel}:${repo}:${bump}`}> | ||
| {panel === 'prs' ? ( | ||
| <PrsPanel repo={repo} enabled={available} /> | ||
| ) : panel === 'issues' ? ( | ||
| <IssuesPanel repo={repo} enabled={available} /> | ||
| ) : panel === 'runs' ? ( | ||
| <RunsPanel repo={repo} enabled={available} /> | ||
| ) : panel === 'releases' ? ( | ||
| <ReleasesPanel repo={repo} enabled={available} /> | ||
| ) : ( | ||
| <SearchPanel enabled={available} /> | ||
| )} | ||
| </div> | ||
| )} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Refresh resets panel-local filters/expansion instead of just refetching.
bump is folded into the remount key (Line 131), so clicking refresh fully unmounts/remounts the active panel. That wipes local UI state each panel keeps for itself — e.g. IssuesPanel's open/closed state filter, PrsPanel's state filter and expanded-checks row — silently reverting them to defaults. useGithubQuery already exposes a non-destructive refresh() (useGithubQuery.ts Lines 15, 27) for exactly this purpose, but no panel consumes it.
Threading a refreshToken prop into each panel's fetcher deps (instead of remounting) would refetch without discarding filter/expansion state.
♻️ Sketch of a non-destructive refresh
- const [bump, setBump] = useState(0)
+ const [refreshToken, setRefreshToken] = useState(0)
@@
- onClick={() => setBump((b) => b + 1)}
+ onClick={() => setRefreshToken((t) => t + 1)}
@@
- <div key={`${panel}:${repo}:${bump}`}>
+ <div key={`${panel}:${repo}`}>
{panel === 'prs' ? (
- <PrsPanel repo={repo} enabled={available} />
+ <PrsPanel repo={repo} enabled={available} refreshToken={refreshToken} />Each panel would then add refreshToken to its fetcher's useCallback deps (e.g. PrsPanel.tsx Line 54) so useGithubQuery refetches without unmounting.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@console/web/src/pages/Github/index.tsx` around lines 46 - 144, Replace the
bump-based remount in the Github page with a refreshToken prop threaded into
PrsPanel, IssuesPanel, RunsPanel, ReleasesPanel, and SearchPanel. Update each
panel’s fetcher callback dependencies to include refreshToken so useGithubQuery
refetches while preserving panel-local filters and expansion state; keep
repository and panel changes functioning normally without using refreshToken in
the container key.
What
New standalone
githubworker (Rust,deploy: binary, v0.1.0) wrapping the GitHub CLI as iii functions:github::pr::*(list/view/create/edit/merge/comment/review/diff/checks),github::issue::*(list/view/create/edit/comment/close),github::repo::*(view/list),github::run::*(list/view/rerun/cancel),github::workflow::*(list/run),github::release::*(list/view/create),github::search::*(repos/issues/prs/code).github::exec(argv passthrough; non-zero exit and timeout are DATA —{stdout, stderr, exit_code, timed_out, *_truncated}) andgithub::api(any REST endpoint,-ffields / JSON body via--input -/--jq/--paginate).Design
shell/src/exec/host.rsmechanics: bounded capture that keeps draining after the cap (SIGPIPE guard preserves real exit codes), process-group SIGKILL on timeout,kill_on_drop, stdin piped only when supplied. Machine-branchable failure codes viaError::Remote(gh_not_found,spawn_failed,timeout).request -> argvbuilders (unit-tested without spawning),-R owner/namealways explicit (the worker has no checkout;repo view/listare positional), non-zero exits becomeError::Handlercarrying gh's stderr — exceptpr::checks, where exit codes {0,1,8} are check-state data.configurationworker (devin pattern):gh_executablePATH fallback,token: "${GH_TOKEN}"injected into child env with ambientgh auth loginfallback, timeout clamp (30s default / 120s max), 1 MiB per-stream caps. No gh probe at boot — interface collection works on bare runners.needs_approval;github::on-config-changedenied.Verification
cargo fmt --check,clippy -D warnings,cargo test(32 tests) all green.exec --version,repo::view cli/cli,pr::list,pr::checks(pending/failed as data),api rate_limit+--jqstring fallback, timeout-as-data, and the 404 error path all behave as designed.Wiring
create-tag.ymlworker option,release.ymltag glob, root README Modules row, rootiii-permissions.yamlrules. First release: Create Tag with worker=github, registry tagnext.Summary by CodeRabbit